package modeller.databasedesignmodel.servicedemand.transaction.sqlquery.demandFetcher;

import exceptions.NoIndexOnAttributesException;
import modeller.databasedesignmodel.Attribute;
import modeller.databasedesignmodel.DatabaseDesign;
import modeller.databasedesignmodel.query.*;
import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.sqlquery.SQLQueryServiceDemand;
import modeller.databasedesignmodel.transaction.SQLQuery;
import queped.NeptuneHelperFunctions;

import java.util.HashSet;

/**
 * Created by:  Jason Ye
 * Date:        23/03/2012
 * Time:        09:20
 */
public class NonUniqueClusteredTreeIndexCalculatorVisitor extends AbstractIndexedSQLQueryCalculatorVisitor {

    public NonUniqueClusteredTreeIndexCalculatorVisitor(DatabaseDesign design) {
        super(design);
    }

    @Override
    public IServiceDemand calculate(SequentialScanQuery query) {

        // BD
        Relation relation = query.getRelation().first;
        return new SQLQueryServiceDemand(
                relation.getBNumDbPages() * design.getDAverageIOTime(),
                query.getRelation().getFirst()
        );

    }

    @Override
    public IServiceDemand calculate(EqualitySearchQuery query) {

        // D * logF(B)

        //assume one relation per query, and required relation is at front of list
        Relation relation = query.getRelation().first;
        double D = design.getDAverageIOTime();

        try {
            return new SQLQueryServiceDemand(
                    D * getLogBaseFofB(relation, query),
                    query.getRelation().getFirst()
            );
        } catch (NoIndexOnAttributesException e) {

            // use default
            return defaultCalculator.calculate(query);
        }

    }

    @Override
    public IServiceDemand calculate(EqualityWithRangeSearchQuery query) {

        // D(logF(B)+#matching pages )
        Relation relation = query.getRelation().first;
        double D = design.getDAverageIOTime();
        //assume one relation per query, and required relation is at front of list
        double logFB = 0;

        try {
            logFB = getLogBaseFofB(relation, query);
        } catch (NoIndexOnAttributesException e) {
            //no index found, use default HeapCalculator
            return defaultCalculator.calculate(query);
        }

        int numMatchingRows = query.getNumMatchingRows();
        double recordsPerPage = Math.ceil( design.getDBPageSize() / (double) relation.getExpectedRowSize());
        double numMatchingPages = Math.ceil(((double) numMatchingRows) / recordsPerPage);

        return new SQLQueryServiceDemand(
                D * (logFB + numMatchingPages),
                query.getRelation().getFirst()
        );
    }

    @Override
    public IServiceDemand calculate(InsertQuery query) {
        // Search + D = D * logF(B) + D = D *( 1 + logF(B))

        //assume one relation per query, and required relation is at front of list
        Relation relation = query.getRelation().first;
        try {
            return getDTimes1PlusLogFRB(relation, query);
        } catch (NoIndexOnAttributesException e) {
            // no index defined
            return defaultCalculator.calculate(query);
        }

    }

    @Override
    public IServiceDemand calculate(UpdateDeleteQuery query) {

        // Search + D = D * logF(B) + D = D *( 1 + logF(B))
        //assume one relation per query, and required relation is at front of list
        Relation relation = query.getRelation().first;
        try {
            return getDTimes1PlusLogFRB(relation, query);
        } catch (NoIndexOnAttributesException e) {
            // no index defined
            return defaultCalculator.calculate(query);
        }

    }

    private double getLogBaseFofB(Relation relation, SQLQuery query) throws NoIndexOnAttributesException {

        double B = relation.getBNumDbPages();

        HashSet<Attribute> attributesInQuery = query.getRelation().second;
        // calculating F requires index and fill factor
        double F = relation.getFIndexTreeFanout(relation.getIndexWithAttributes(attributesInQuery), design.getDBFillFactor());

        return NeptuneHelperFunctions.logBaseArbitrary(F, B);


    }

    private SQLQueryServiceDemand getDTimes1PlusLogFRB(Relation relation, SQLQuery query) throws NoIndexOnAttributesException {

        //assume one relation per query, and required relation is at front of list
        double D = design.getDAverageIOTime();

        return new SQLQueryServiceDemand(
                D * (1 + getLogBaseFofB(relation, query)),
                query.getRelation().getFirst()
        );

    }


}